Skip to main content

Aborting Tasks

Piscina allows submitted tasks to be cancelled even if the task has already been submitted to a worker and is being actively processed. This can be used, for instance, to cancel tasks that are taking too long to run.

In this example, we'll simulate a long running task using setTimeout:

worker.js
'use strict';

const { promisify } = require('util');
const sleep = promisify(setTimeout);

module.exports = async ({ a, b }) => {
await sleep(10000);
return a + b;
};
worker.ts
import { promisify } from 'util';
import { resolve } from 'path';

export const filename = resolve(__filename);

const sleep = promisify(setTimeout);

interface OperationInput {
a: number;
b: number;
}

export default async ({ a, b }: OperationInput): Promise<number> => {
await sleep(10000);
return a + b;
};

Tasks can be canceled using an AbortController or an EventEmitter.

Using AbortController​

In this example, the AbortController instance is passed as the signal option to the piscina.run method. After submitting the task, we call abortController.abort() to cancel the task.

"use strict";

const Piscina = require("piscina");
const { AbortController } = require("abort-controller");
const { resolve } = require("path");

const piscina = new Piscina({
filename: resolve(__dirname, "worker.js"),
});

(async function () {
const abortController = new AbortController();
try {
const task = piscina.run(
{ a: 4, b: 6 },
{ signal: abortController.signal }
);
abortController.abort();
await task;
} catch (err) {
console.log("The task was cancelled");
}
})();

Using EventEmitter​

The EventEmitter instance is passed as the signal option to the piscina.run method.

'use strict';

const Piscina = require('../..');
const EventEmitter = require('events');
const { resolve } = require('path');

const piscina = new Piscina({
filename: resolve(__dirname, 'worker.js')
});

(async function () {
const ee = new EventEmitter();
try {
const task = piscina.run({ a: 4, b: 6 }, { signal: ee });
ee.emit('abort');
await task;
} catch (err) {
console.log('The task was cancelled');
}
})();

Using a Timer with EventEmitter​

'use strict';

const Piscina = require('../..');
const EventEmitter = require('events');
const { resolve } = require('path');

const piscina = new Piscina({
filename: resolve(__dirname, 'worker.js')
});

(async function () {
const ee = new EventEmitter();
// Use a timer to limit task processing length
const t = setTimeout(() => ee.emit('abort'), 500);
try {
await piscina.run({ a: 4, b: 6 }, { signal: ee });
} catch (err) {
console.log('The task timed out');
} finally {
// Clear the timeout in a finally to make sure
// it is definitely cleared.
clearTimeout(t);
}
})();

Using a Timer with AbortController​

'use strict';

const Piscina = require('../..');
const { resolve } = require('path');

const piscina = new Piscina({
filename: resolve(__dirname, 'worker.js')
});

(async function () {
// Using the new built-in Node.js AbortController
// Node.js version 15.0 or higher

const ac = new AbortController();

// Use a timer to limit task processing length
const t = setTimeout(() => ac.abort(), 500);
try {
await piscina.run({ a: 4, b: 6 }, { signal: ac.signal });
} catch (err) {
console.log('The task timed out');
} finally {
// Clear the timeout in a finally to make sure
// it is definitely cleared.
clearTimeout(t);
}
})();

You can also check out this example on github.